using System;
using System.Xml;
using System.Text.RegularExpressions;

namespace Lookstone.Configuration
{
	/// <summary>
	/// A Settings object acts similarly to the Windows registry, 
	/// yet is stored as an XML file at the target location
	/// </summary>
	public class Settings : IDisposable
	{
		// Member variables
		bool _isDirty;
		XmlDocument _xmlDoc;
		string _storeName;
		string _dataPath;

		/// <summary>
		/// Constructs a settings object
		/// </summary>
		/// <param name="settingsStoreName">The root level name of the settings file</param>
		/// <param name="settingsPath">The full file path of the settings file</param>
		public Settings(string settingsStoreName, string settingsPath)
		{
			_dataPath = settingsPath;
			_storeName = settingsStoreName;
			_xmlDoc = new XmlDocument();

			// Load the dataset
			Load();
		}

		~Settings()
		{
			Dispose(false);
		}

		public void Dispose()
		{
			Dispose(true);

			GC.SuppressFinalize(this);
		}

		protected virtual void Dispose(bool disposing)
		{
			if (_isDirty)
				Save();

			if (disposing)
			{
				// dispose managed resources
				_xmlDoc = null;
				_storeName = null;
				_dataPath = null;
			}

			// dispose unmanaged resources
		}

		/// <summary>
		/// Will persist the settings file to disk
		/// </summary>
		public void Save()
		{
			try
			{
				_xmlDoc.Save(_dataPath);
				_isDirty = false;
			}
			catch (Exception)
			{
			}
		}

		public string this[string fullKeyNamePath]
		{
			get { return GetSetting(fullKeyNamePath); }
			set { SetSetting(fullKeyNamePath, value); }
		}

		public string this[string key, string name]
		{
			get { return GetSetting(key, name); }
			set { SetSetting(key, name, value); }
		}

		public string GetSetting(string fullKeyNamePath)
		{
			XmlNode node = GetNode(fullKeyNamePath, false);

			if (node == null || !node.HasChildNodes)
				return "";

			return ((XmlElement)node).InnerText;
		}

		public string GetSetting(string key, string name)
		{
			string masterKey = string.Format("{0}\\{1}", key, name);

			return GetSetting(masterKey);
		}

		public int? GetSettingInt(string key, string name)
		{
			string masterKey = string.Format("{0}\\{1}", key, name);
			string setting = GetSetting(masterKey);

			int value;

			if (setting.Length > 0 && int.TryParse(setting, out value))
				return value;

			return new int?();
		}

		public int GetSettingInt(string key, string name, int defaultVal)
		{
			return GetSettingInt(key, name) ?? defaultVal;
		}

		public long? GetSettingLong(string key, string name)
		{
			string masterKey = string.Format("{0}\\{1}", key, name);
			string setting = GetSetting(masterKey);

			long value;

			if (setting.Length > 0 && long.TryParse(setting, out value))
				return value;

			return new long?();
		}

		public long GetSettingLong(string key, string name, long defaultVal)
		{
			return GetSettingLong(key, name) ?? defaultVal;
		}

		public bool GetSettingBool(string key, string name, bool defaultVal)
		{
			string masterKey = string.Format("{0}\\{1}", key, name);
			string setting = GetSetting(masterKey);

			try
			{
				if (setting.Length > 0)
					return bool.Parse(setting);
			}
			catch
			{
				return defaultVal;
			}

			return defaultVal;
		}

		public string GetSettingString(string key, string name, string defaultVal)
		{
			string masterKey = string.Format("{0}\\{1}", key, name);
			XmlNode node = GetNode(masterKey, false);

			if (node != null)
				return ((XmlElement)node).InnerText;

			// If it wasn't found, return the default value
			return defaultVal;
		}

		public void SetSetting(string key, string name, string settingValue)
		{
			string masterKey = string.Format("{0}\\{1}", key, name);

			SetSetting(masterKey, settingValue);
		}

		public void SetSetting(string fullKeyNamePath, string settingValue)
		{
			XmlNode node = GetNode(fullKeyNamePath, true);

			// Only update it if it actually changed
			if (settingValue.CompareTo(((XmlElement)node).InnerText) != 0)
			{
				((XmlElement)node).InnerText = settingValue;
				_isDirty = true;
			}
		}

		private bool Load()
		{
			bool hasElement = true;

			_isDirty = false;
			if (System.IO.File.Exists(_dataPath))
			{
				try
				{
					_xmlDoc.Load(_dataPath);
				}
				catch
				{
					hasElement = false;
				}

				if (!hasElement || _xmlDoc.DocumentElement == null)
				{
					_xmlDoc.RemoveAll();
					_xmlDoc.AppendChild(_xmlDoc.CreateElement(_storeName));
					return false;
				}
				return true;
			}
			else
			{
				_xmlDoc.AppendChild(_xmlDoc.CreateElement(_storeName));
			}

			return false;
		}

		private XmlNode GetNode(string key, bool createNodeIfNoExist)
		{
			XmlNode currentNode = _xmlDoc.DocumentElement;
			string xpathKey = key.Replace('\\', '/');

			// Make sure to remove all spaces from the element names
			xpathKey = xpathKey.Replace(" ", "");

			if (currentNode == null)
			{
				_xmlDoc.AppendChild(_xmlDoc.CreateElement(_storeName));
				currentNode = _xmlDoc.DocumentElement;
			}

			XmlNode matchingNode = currentNode.SelectSingleNode(xpathKey);

			// If the node was not found, and it is not requested to create 
			// the node, then return null
			if (matchingNode == null && !createNodeIfNoExist)
				return null;
			else if (matchingNode != null)
				return matchingNode;

			// Create the node down to the level
			string[] keys = xpathKey.Split(new char[1] { '/' });

			for (int pos = 0; pos < keys.Length; pos++)
			{
				matchingNode = currentNode.SelectSingleNode(keys[pos]);
				if (matchingNode == null)
				{
					// Create a child node, because it did not already exist
					XmlElement elem = _xmlDoc.CreateElement(keys[pos]);
					currentNode.AppendChild(elem);
					currentNode = elem;
				}
				else // search for the next child node
					currentNode = matchingNode;
			}

			return currentNode;
		}

		public bool Dirty
		{
			get { return _isDirty; }
		}
	}
}